#include "tagfs.h"
#include <cerrno>

// ---------------------------------------------------
bool TagFS_Node::nodeRange::operator<  (TagFS_Node::nodeRange r) { return (this->start  < r.start); }
bool TagFS_Node::nodeRange::operator>  (TagFS_Node::nodeRange r) { return (this->start  > r.start); }
bool TagFS_Node::nodeRange::operator== (TagFS_Node::nodeRange r) { return (this->start == r.start); }
// ---------------------------------------------------

// --------------------------------------------------------------
void TagFS_Node::init(FILE* f, uint8_t o, uint16_t s, uint64_t c)
// --------------------------------------------------------------
{
	fs = f;
	offset = o;
	nodeSize = s;
	maxNodes = c;
}

// ---------------------------------
uint16_t TagFS_Node::getSize()
// ---------------------------------
{
	return nodeSize-8;
}

// ---------------------------------
uint64_t TagFS_Node::getAll()
// ---------------------------------
{
	return maxNodes;
}

// ---------------------------------
uint64_t TagFS_Node::getUsed()
// ---------------------------------
{
	uint64_t used = 0;
	std::list<nodeRange>::iterator i;
	for (i=usedNodes.begin(); i!=usedNodes.end(); ++i)
	{
		used += i->length;
	}
	return used;
}

// ----------------------------------------------------------------
std::list<uint64_t> TagFS_Node::getList(uint64_t nextNode)
// ----------------------------------------------------------------
{
	std::list<uint64_t> list;
	list.push_back(nextNode);

	char buf[8];
	fseeko(fs, offset+(nextNode*nodeSize), SEEK_SET);
	uint16_t size = fread(buf, 1, 8, fs);
	nextNode = *(uint64_t*)(buf);

	while (nextNode != 0)
	{
		list.push_back(nextNode);
		fseeko(fs, offset+(nextNode*nodeSize), SEEK_SET);
		size = fread(buf, 1, 8, fs);
		nextNode = *(uint64_t*)(buf);
	}

	return list;
}

// --------------------------------------------------------------
void TagFS_Node::cleanup(std::list<TagFS_Node::nodeRange>* nodes)
// --------------------------------------------------------------
{
	std::list<nodeRange>::iterator i;

	nodes->sort();

	i = nodes->begin();
	uint64_t tmpStart = i->start;
	uint64_t tmpLength = i->length;
	++i;
	for (i; i!=nodes->end(); ++i)
	{
		if (i->start <= tmpStart+tmpLength)
		{
			tmpStart = i->start;
			tmpLength = i->length;
			--i;
			if (i->start+i->length < tmpStart+tmpLength)
			{
				i->length += (tmpStart+tmpLength) - (i->start+i->length);
			}
			++i;
			nodes->erase(i);
			--i;
		}
		tmpStart = i->start;
		tmpLength = i->length;
	}
}

// ----------------------------------------------------------------
std::pair<uint64_t, int> TagFS_Node::read(uint64_t no, char* data)
// ----------------------------------------------------------------
{
	std::pair<uint64_t, int> res;
	res.second = 0;
	if (no >= maxNodes)
	{
		res.second = -ESPIPE;
		return res;
	}
	res.first = 0;
	res.second = 0;
	fseeko(fs, offset+(no*nodeSize), SEEK_SET);
	uint64_t read = fread(data, 1, 8, fs);
	if (read != 8)
	{
		res.second = -EIO;
		return res;
	}
	res.first = *(uint64_t*)(data);
	read += fread(data, 1, nodeSize-8, fs);
	if (read != nodeSize)
	{
		res.second = -EIO;
	}
	return res;
}

// -------------------------------------------------------------------------------------
std::pair<uint64_t, int> TagFS_Node::write(uint64_t no, const char* data, bool nextplz)
// -------------------------------------------------------------------------------------
{
	std::pair<uint64_t, int> res;
	res.first = 0;
	res.second = 0;
	if (no >= maxNodes)
	{
		res.second = -ESPIPE;
		return res;
	}
	res.first = 0;
	res.second = 0;
	if (nextplz)
	{
		res.first = reserve();
		if (res.first == 0)
		{
			res.second = -ENOSPC;
			return res;
		}
	}
	fseeko(fs, offset+(no*nodeSize), SEEK_SET);
	uint16_t wrote = fwrite((char*)(&res.first), 1, 8, fs);
	if (wrote != 8)
	{
		res.second = -EIO;
	}
	wrote = fwrite(data, 1, nodeSize-8, fs);
	if (wrote != nodeSize-8)
	{
		res.second = -EIO;
	}
	return res;
}

// -------------------------------------------------
int TagFS_Node::write(uint64_t no, const char* data)
// -------------------------------------------------
{
	if (no >= maxNodes)
	{
		return -EIO;
	}
	fseeko(fs, offset+(no*nodeSize)+8, SEEK_SET);
	uint16_t wrote = fwrite(data, 1, nodeSize-8, fs);
	if (wrote != nodeSize-8)
	{
		return -EIO;
	}
	return 0;
}

// ----------------------------------
void TagFS_Node::reserve(uint64_t no)
// ----------------------------------
{
	nodeRange range;
	range.start = no;
	range.length = 1;
	usedNodes.push_back(range);
	cleanup(&usedNodes);
}

// -----------------------------------------------
void TagFS_Node::reserve(std::list<uint64_t> list)
// -----------------------------------------------
{
	nodeRange range;
	range.length = 1;
	std::list<uint64_t>::iterator i;
	for (i=list.begin(); i!=list.end(); ++i)
	{
		range.start = *i;
		usedNodes.push_back(range);
	}
	cleanup(&usedNodes);
}

// ---------------------------
uint64_t TagFS_Node::reserve()
// ---------------------------
{
	std::list<nodeRange>::iterator i;
	uint64_t freenode = 0;
	for (i=usedNodes.begin(); i!=usedNodes.end(); ++i)
	{
		if (freenode < i->start)
		{
			break;
		}
		freenode = i->start + i->length;
	}
	if (freenode < maxNodes)
	{
		nodeRange range;
		range.start = freenode;
		range.length = 1;
		usedNodes.push_back(range);
		cleanup(&usedNodes);
		// printf("!!! reserved %llu/%llu\n", freenode, maxNodes-1);
		return freenode;
	}
	return 0;
}

// ---------------------------------
void TagFS_Node::free(uint64_t node)
// ---------------------------------
{
	std::list<nodeRange>::iterator u;
	for (u=usedNodes.begin(); u!=usedNodes.end(); ++u)
	{
		if (u->start == node)
		{
			u->start = u->start + 1;
			break;
		}
		else if (u->start+u->length == node)
		{
			u->length = u->length - 1;
			break;
		}
		else if (u->start < node && node < u->start+u->length)
		{
			nodeRange newrange;
			newrange.start = node + 1;
			newrange.length = u->start + u->length - node - 1;
			u->length = node - u->start;
			usedNodes.push_back(newrange);
			break;
		}
	}
	cleanup(&usedNodes);
}

// ---------------------------------------------
void TagFS_Node::free(std::list<uint64_t> list)
// ---------------------------------------------
{
	std::list<uint64_t>::iterator l;
	for (l=list.begin(); l!=list.end(); ++l)
	{
		free(*l);
	}
}

